import 'dart:convert';
import 'dart:isolate';

import 'package:im_app/im/socket/dto/base/Resp.dart';
import 'package:im_app/im/socket/dto/business/Login.dart';
import 'package:im_app/im/socket/dto/business/RpcData.dart';
import 'package:logger/logger.dart';
import 'package:web_socket_channel/io.dart';


const String SPLIT = " ";
const String NEWLINE = "\r\n";

class ImWebSocket3 {

  final String _websokcetUrl;
  late Isolate _isolate;
  late SendPort _isolateSendPort;

  var _logger = Logger(printer: PrettyPrinter(),);

  ImWebSocket3(this._websokcetUrl) {}

  Future<void> start() async {
    ReceivePort receivePort = new ReceivePort();
    _isolate = await Isolate.spawn(doRunner, receivePort.sendPort);
    _isolateSendPort = await receivePort.first as SendPort;
    _logger.i("websocket client start!!!");
  }

  Future<RpcData> sendData(RpcData rpcData, Function function) async {
    ReceivePort sendDataReceivePort = new ReceivePort();
    try {
      assert(_isolateSendPort != null, "_isolateSendPort must be not null");
      _isolateSendPort.send(_SendData.from(rpcData: rpcData,
          decodeFunction: function,
          sendPort: sendDataReceivePort.sendPort));
      _logger.i("send message path: ${rpcData.path}");
      RpcData result = await sendDataReceivePort.first.timeout(
          Duration(seconds: 10)) as RpcData;
      return result;
    } finally {
      sendDataReceivePort.close();
    }
  }

  void callback(String path, Function decodeFunction, Function doFunction) async {
    ReceivePort sendDataReceivePort = new ReceivePort();
    assert(_isolateSendPort != null, "_isolateSendPort must be not null");
    _isolateSendPort.send(_CallBackData(path, decodeFunction, doFunction, sendDataReceivePort.sendPort));
    _logger.i("send message path: $path");
    sendDataReceivePort.listen((message) {
      doFunction(message);
    });
  }

  void timeout(RpcData rpcData) {
    _isolateSendPort.send(_TimeoutData(rpcData));
  }

  void close() {
    _isolate?.kill();
  }

  RpcData _decodeRpcData(Map map, String headStr, String extData, String data) {
    List<String> headers = headStr.trim().split(SPLIT);
    print("header: $headers");
    assert(headers.length == 3, "头部信息格式不对");
    String path = headers[0];
    int version = int.parse(headers[1]);
    int status = int.parse(headers[2]);

    Map<String, String> extDataMap = {};
    if (extData != null || extData != "") {
      print("extData: $extData");
      List<String> entryValues = extData.split(NEWLINE);
      entryValues.forEach((entry) {
        List<String> entryStr = entry.split(":");
        extDataMap[entryStr[0]] = entryStr[1];
      });
    }

    String seq = extDataMap['localSeq'].toString();
    if (status == -1) {
      ExceptionResp exceptionResp = ExceptionResp.fromJson(jsonDecode(data));
      RpcData rpcData = RpcData(path, version, status, extDataMap, exceptionResp);
      return rpcData;
    }
    assert(seq != null, "返回值中seq不能为空");
    _InnerData innerData = map[seq];
    Function? function;
    if (innerData != null) {
      function = innerData.message.decodeFunction;
    }
    if (function == null) {
      function = map[extDataMap['path']].decodeFunction;
    }
    assert(function != null, "function不能为空");

    var resultData = null;
    // print("data: $data");
    if (function != null) {
      resultData = function(jsonDecode(data));
    } else {
      resultData = jsonDecode(data);
    }
    return RpcData(path, version, status, extDataMap, resultData);
  }



  void _listener(IOWebSocketChannel webSocketChannel, Map map, Map session) {
    webSocketChannel.stream.listen((dynamic message) {
      _logger.i("start recevie message $message");
      var content = message.toString();
      int idx = content.indexOf(NEWLINE);
      int secondIdx = content.indexOf(NEWLINE + NEWLINE);
      String headerStr = content.substring(0, idx);
      _logger.i("start receive message header : $headerStr");
      String extData = "";
      if (idx < secondIdx - 2) {
        extData = content.substring(idx+2, secondIdx);
        extData = extData.trim();
      }
      _logger.i("start receive message extData : $extData");
      String data = content.substring(secondIdx + 2);
      data = data.trim();
      // _logger.i("start receive message data : $data");
      RpcData rpcData = _decodeRpcData(map, headerStr, extData, data);
      if (rpcData.data is LoginResp) {
        LoginResp loginResp = rpcData.data as LoginResp;
        session['authToken'] = loginResp.authToken;
        session['userId'] = loginResp.userId;
        session['tenantCode'] = loginResp.tenantCode;
        session['authCode'] = "${loginResp.authCode}";
      }
      String seq = rpcData.extDataMap['localSeq'].toString();
      _InnerData innerData = map.remove(seq);
      if (innerData != null && innerData.message != null) {
        SendPort? sendPort = innerData.message.sendPort;
        if (sendPort != null) {
          _logger.i("send port result");
          sendPort.send(rpcData);
        }
        if (innerData.sendPort != null) {
          innerData.sendPort?.send(rpcData);
        }
      } else { // callback
        String path = rpcData.extDataMap['path'].toString();
        _CallBackData callBackData = map[path];
        callBackData.sendPort?.send(rpcData);
      }
      // _logger.i("end recevie message $content");
    });
  }

  Future<LoginResp?> _reconnection(IOWebSocketChannel webSocketChannel, Map callbackMap, Map session) async {
    if (session.containsKey("authToken")) {
      ReConnection reConnection = ReConnection(authToken: session['authToken'], tenantCode: session['tenantCode'],
        terminalType: "android");
      // android
      Map<String, String> extDataMap = {"localSeq": "2"};
      RpcData rpcData = RpcData("reconnection", 1, 0, extDataMap, reConnection);
      RpcData result = await _doSendMessageSync(webSocketChannel, callbackMap, session, _SendData(rpcData,
          LoginResp.fromJson));
      _logger.i("recevie data: " + jsonEncode(result.data));
      return result.data as LoginResp;
    }
  }

  void _doSendMessage(IOWebSocketChannel? webSocketChannel, Map callbackMap, Map session, _SendData message) {
    RpcData rpcData = message.rpcData;
    if (session['authCode'] != null) {
      rpcData.extDataMap['authCode'] = session['authCode'];
    }
    String data = jsonEncode(rpcData.data);
    String head = rpcData.path + SPLIT + rpcData.version.toString() + SPLIT + rpcData.status.toString() + SPLIT + NEWLINE;
    rpcData.extDataMap.forEach((key, value) {
      head += "$key:$value$NEWLINE";
    });
    String body = head + NEWLINE + data;
    webSocketChannel?.sink.add(body);
    String seq = rpcData.extDataMap["localSeq"].toString();
    assert(seq != null, "seq 不能为空");
    callbackMap[seq] = _InnerData(message, null);
    _logger.i("send message body $body");
  }

  Future? _doSendMessageSync(IOWebSocketChannel? webSocketChannel, Map callbackMap, Map session,
      _SendData message) {
    RpcData rpcData = message.rpcData;
    if (session['authCode'] != null) {
      rpcData.extDataMap['authCode'] = session['authCode'];
    }
    String data = jsonEncode(rpcData.data);
    String head = rpcData.path + SPLIT + rpcData.version.toString() + SPLIT + rpcData.status.toString() + SPLIT + NEWLINE;
    rpcData.extDataMap.forEach((key, value) {
      head += "$key:$value$NEWLINE";
    });
    String body = head + NEWLINE + data;
    webSocketChannel?.sink.add(body);
    String seq = rpcData.extDataMap["localSeq"].toString();
    assert(seq != null, "seq 不能为空");
    ReceivePort receivePort = new ReceivePort();
    callbackMap[seq] = _InnerData(message, receivePort.sendPort);
    _logger.i("send message body $body");
    return receivePort.first;
  }

  void _doCallBackMessage(IOWebSocketChannel? webSocketChannel, Map callbackMap, Map session, _CallBackData message) {
    callbackMap[message.path] = message;
  }
}

void doRunner(SendPort port) async {
  // SendPort port = isloateInitData.sendPort;
  // String websocketUrl = isloateInitData.webSocketUrl;
  ReceivePort receivePort = new ReceivePort();
  port.send(receivePort.sendPort);

  // IOWebSocketChannel webSocketChannel = IOWebSocketChannel.connect(websocketUrl);
  // Map<String, dynamic> callBackMap = {};
  // Map<String, String> session = {};
  // _listener(webSocketChannel, callBackMap, session);
  // ReceivePort receivePort = new ReceivePort();
  // port.send(receivePort.sendPort);
  // receivePort.listen((dynamic message) async {
  //   _logger.i("receive message");
  //   if (webSocketChannel == null || webSocketChannel.closeReason != null) {
  //     try {
  //       _logger.i("websocket reconnection!!!");
  //       webSocketChannel = IOWebSocketChannel.connect(websocketUrl);
  //       _logger.i("websocket listener!!!");
  //       _listener(webSocketChannel, callBackMap, session);
  //       await _reconnection(webSocketChannel, callBackMap, session);
  //       _logger.i("websocket _reconnection!!!");
  //     } catch(e) {
  //       _logger.e("连接websocket失败", error: e);
  //       return ;
  //     }
  //   }
  //   if (message is _SendData) {
  //     _doSendMessage(webSocketChannel, callBackMap, session, message);
  //   } else if (message is _CallBackData){
  //     _doCallBackMessage(webSocketChannel, callBackMap, session, message);
  //   }  else if (message is _TimeoutData){
  //     String? seq = message.rpcData.extDataMap['localSeq'].toString();
  //     callBackMap.remove(seq);
  //   }else {
  //     _logger.e("不支持这种类型, ${message.runtimeType}");
  //   }
  // });
}

class _SendData {
  final RpcData rpcData;
  final Function decodeFunction;
  SendPort? sendPort;

  _SendData(this.rpcData, this.decodeFunction);

  _SendData.from({required this.rpcData, required this.decodeFunction, this.sendPort});
}

class _TimeoutData {
  final RpcData _rpcData;

  _TimeoutData(this._rpcData);

  RpcData get rpcData => _rpcData;
}

class _CallBackData {
  final String _path;
  final Function _decodeFunction;
  final Function _serviceFunction;
  final SendPort? _sendPort;

  _CallBackData(this._path, this._decodeFunction, this._serviceFunction, this._sendPort);

  String get path => _path;
  Function get decodeFunction => _decodeFunction;
  Function get serviceFunction => _serviceFunction;
  SendPort? get sendPort => _sendPort;
}

class _InnerData {
  final _SendData _message;
  final SendPort? _sendPort;

  _InnerData(this._message, this._sendPort);

  _SendData get message => _message;
  SendPort? get sendPort => _sendPort;
}

class _IsloateInitData {
  final String _webSocketUrl;
  final SendPort _sendPort;

  _IsloateInitData(this._webSocketUrl, this._sendPort);

  String get webSocketUrl => _webSocketUrl;
  SendPort get sendPort => _sendPort;
}